﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Shapes.Geometry;
using System.Xml;
using Microsoft.Xna.Framework;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Shapes.Misc.Serialization
{

#if !XBOX && !WINDOWS_PHONE && !ZUNE
    /// <summary>
    /// The ShapesFactory provides functionality to save and load shapes
    /// </summary>
    public static class ShapesFactory
    {
        /// <summary>
        /// Saves all passed shapes to an xml file
        /// </summary>
        /// <param name="filepath">the path, filename and extension of the file to save</param>
        /// <param name="saveTransform">indicates wether the transformation data of the shapes should be saved or not</param>
        /// <param name="geometries">all geometriy to save</param>
        public static void SaveXml(string filepath, bool saveTransform, params Drawing[] geometries)
        {
            XmlDocument doc = new XmlDocument();
            XmlNode root = doc.AppendChild(doc.CreateElement("Shapes"));

            SaveXml(doc, root, saveTransform, geometries);
            doc.Save(filepath);
        }
        /// <summary>
        /// Inserts the passed shapes into the xml document at the given node
        /// </summary>
        /// <param name="doc">the XmlDocument which shall save the shapes</param>
        /// <param name="parentNode">the parent node of the shapes nodes</param>
        /// <param name="saveTransform">indicates wether the transformation data of the shapes should be saved or not</param>
        /// <param name="geometries">all geometriy to save</param>
        public static void SaveXml(XmlDocument doc, XmlNode parentNode, bool saveTransform, params Drawing[] geometries)
        {
            foreach (Drawing g in geometries)
            {
                SerializeXml(g, saveTransform, parentNode, doc);
            }
        }

        /// <summary>
        /// loads an array of shapes from an xml file
        /// </summary>
        /// <param name="filePath">the path, filename and extension of the file to load</param>
        /// <param name="loadTransform">indicates wether the transformation data of the shapes should be loaded or not</param>
        /// <returns>an array of geometries</returns>
        public static Drawing[] LoadXml(string filePath, bool loadTransform)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(filePath);

            XmlNode root = doc.FirstChild;
            if (root.Name != "Shapes")
                throw new Exception("unknown XML format");

            return LoadXml(doc, root, loadTransform);
        }
        /// <summary>
        /// loads an array of shapes from the passed document inside the given node
        /// </summary>
        /// <param name="doc">the XmlDocument which contains the shapes</param>
        /// <param name="parentNode">the parent node of the shapes nodes</param>
        /// <param name="loadTransform">indicates wether the transformation data of the shapes should be loaded or not</param>
        /// <returns>an array of geometries</returns>
        public static Drawing[] LoadXml(XmlDocument doc, XmlNode parentNode, bool loadTransform)
        {
            List<Drawing> list = new List<Drawing>();
            foreach (XmlNode node in parentNode.ChildNodes)
            {
                Drawing d = DeserializeXml(node, loadTransform);
                if(d != null)
                    list.Add(d);
            }

            return list.ToArray();
        }
        /// <summary>
        /// Saves one Drawing or Shape to Xml
        /// </summary>
        /// <param name="geometry">the geometry to serialize</param>
        /// <param name="saveTransformation">indicates wether the transformation data of the shapes should be saved or not</param>
        /// <param name="parent">the parent node where the geometry-node is appended to</param>
        /// <param name="document">the xml document which stores the parent node</param>
        /// <returns>the xml node for the geometry which is appended to the parent after this method call</returns>
        public static XmlNode SerializeXml(Drawing geometry, bool saveTransformation, XmlNode parent, XmlDocument document)
        {
            XmlNode node = parent.AppendChild(document.CreateElement(geometry.GetGeometryType().ToString()));

            if (saveTransformation)
                SerializeTransformation(geometry._Transform, false, node, document);

            switch (geometry.GetGeometryType())
            {
                case GeometryType.Dot:
                    SerializeDot(geometry as Dot, node);
                    break;
                case GeometryType.Line:
                    SerializeLine(geometry as Line, node);
                    break;
                case GeometryType.LineStrip:
                    SerializeLineStrip(geometry as LineStrip, node);
                    break;
                case GeometryType.Spline:
                    SerializeSpline(geometry as Spline, node);
                    break;
                case GeometryType.Circle:
                    SerializeCircle(geometry as Ellipse, node);
                    break;
                case GeometryType.Ellipse:
                    SerializeEllipse(geometry as Ellipse, node);
                    break;
                case GeometryType.Rect:
                    SerializeRect(geometry as Rect, node);
                    break;
                case GeometryType.Triangle:
                    SerializeTriangle(geometry as Triangle, node);
                    break;
                case GeometryType.Polygon:
                    SerializeLineStrip((geometry as Polygon).LineStrip, node);
                    break;
                case GeometryType.SplinePoly:
                    SerializeSpline((geometry as SplinePoly).Spline, node);
                    break;
                case GeometryType.SpriteShape:
                    SerializeSpriteShape(geometry as SpriteShape, node);
                    break;
                case GeometryType.ShapeComposition:
                    SerializeShapeComposition(geometry as ShapeComposition, node);
                    break;
                default:
                    throw new ArgumentException("the given shape couldn't be serialized", geometry.GetGeometryType().ToString());
            }
            return node;
        }
        /// <summary>
        /// Deserializes a Drawing or Shape from an xml node
        /// </summary>
        /// <param name="node">the node of the geometry</param>
        /// <param name="loadTransform">indicates wether the transformation data of the shapes should be loaded or not</param>
        /// <returns>the Geometry of the node</returns>
        public static Drawing DeserializeXml(XmlNode node, bool loadTransform)
        {
            GeometryType type = (GeometryType)Enum.Parse(typeof(GeometryType), node.Name);
            switch (type)
            {
                case GeometryType.Dot:
                    return DeserializeXml<Dot>(node, loadTransform);
                case GeometryType.Line:
                    return DeserializeXml<Line>(node, loadTransform);
                case GeometryType.LineStrip: ;
                    return DeserializeXml<LineStrip>(node, loadTransform);
                case GeometryType.Spline:
                    return DeserializeXml<Spline>(node, loadTransform);
                case GeometryType.Circle:
                    return DeserializeXml<Ellipse>(node, loadTransform);
                case GeometryType.Ellipse:
                    return DeserializeXml<Ellipse>(node, loadTransform);
                case GeometryType.Rect:
                    return DeserializeXml<Rect>(node, loadTransform);
                case GeometryType.Triangle:
                    return DeserializeXml<Triangle>(node, loadTransform);
                case GeometryType.Polygon:
                    return DeserializeXml<Polygon>(node, loadTransform);
                case GeometryType.SplinePoly:
                    return DeserializeXml<SplinePoly>(node, loadTransform);
                case GeometryType.SpriteShape:
                    return DeserializeXml<SpriteShape>(node, loadTransform);
                case GeometryType.ShapeComposition:
                    return DeserializeXml<ShapeComposition>(node, loadTransform);
                default:
                    return null;
                //    throw new ArgumentException("the given shape couldn't be deserialized", type.ToString());
            }
        }
        /// <summary>
        /// Deserializes a Drawing or Shape from an xml node
        /// </summary>
        /// <typeparam name="T">the type of geometry</typeparam>
        /// <param name="node">the node of the geometry</param>
        /// <param name="loadTransform">indicates wether the transformation data of the shapes should be loaded or not</param>
        /// <returns>the Geometry</returns>
        public static T DeserializeXml<T>(XmlNode node, bool loadTransform) 
            where T : Drawing
        {
            T geometry = FormatterServices.GetUninitializedObject(typeof(T)) as T;
            if (node.Name != geometry.GetGeometryType().ToString() 
                && !((node.Name == GeometryType.Ellipse.ToString() && geometry.GetGeometryType() == GeometryType.Circle)))
                throw new Exception("unexpected node name");

            geometry._Transform = new Transformation2D();
            if (loadTransform)
            {
                foreach (XmlNode n in node.ChildNodes)
                {
                    if(n.Name == "Transform")
                    {
                        DeserializeTransform(n, geometry._Transform, false);
                        break;
                    }
                }
            }

            switch (geometry.GetGeometryType())
            {
                case GeometryType.Dot:
                    DeserializeDot(geometry as Dot, node);
                    break;
                case GeometryType.Line:
                    DeserializeLine(geometry as Line, node);
                    break;
                case GeometryType.LineStrip:
                    DeserializeLineStrip(geometry as LineStrip, node);
                    break;
                case GeometryType.Spline:
                    DeserializeSpline(geometry as Spline, node);
                    break;
                case GeometryType.Circle:
                    DeserializeCircle(geometry as Ellipse, node);
                    break;
                case GeometryType.Ellipse:
                    DeserializeEllipse(geometry as Ellipse, node);
                    break;
                case GeometryType.Rect:
                    DeserializeRect(geometry as Rect, node);
                    break;
                case GeometryType.Triangle:
                    DeserializeTriangle(geometry as Triangle, node);
                    break;
                case GeometryType.Polygon:
                    DeserializePolygon((geometry as Polygon), node);
                    break;
                case GeometryType.SplinePoly:
                    DeserializeSplinePoly((geometry as SplinePoly), node);
                    break;
                case GeometryType.SpriteShape:
                    DeserializeSpriteShape(geometry as SpriteShape, node);
                    break;
                case GeometryType.ShapeComposition:
                    ShapeComposition shapeComp = geometry as ShapeComposition;
                    DeserializeShapeComposition(ref shapeComp, node);
                    geometry = shapeComp as T;
                    break;
                default:
                    geometry = null;
                    break;
                    //throw new ArgumentException("the given shape couldn't be deserialized", geometry.GetGeometryType().ToString());
            }

            return geometry;
        }

        #region Serialize and Deserialize Geometry

        private static void DeserializeSplinePoly(SplinePoly splinePoly, XmlNode node)
        {
            splinePoly._Spline = FormatterServices.GetUninitializedObject(typeof(Spline)) as Spline;
            splinePoly._Spline._Transform = splinePoly._Transform;
            DeserializeSpline(splinePoly.Spline, node);
            splinePoly.Spline.Close();
        }

        private static void DeserializePolygon(Polygon polygon, XmlNode node)
        {

            polygon._LineStrip = FormatterServices.GetUninitializedObject(typeof(LineStrip)) as LineStrip;
            polygon.LineStrip._Transform = polygon._Transform;
            DeserializeLineStrip(polygon.LineStrip, node);
            polygon.LineStrip.Close();
            polygon.SortLinesByY();
        }



        private static void SerializeShapeComposition(ShapeComposition composition, XmlNode node)
        {
            for(int i = composition._Objects.Count - 1; i >= 0; i--)
            {
                ShapeComposition.ShapeObject so = composition._Objects[i];
                XmlNode n = node.AppendChild(node.OwnerDocument.CreateElement("Shape"));
                AppendAttribute(n, "Nr", (composition._Objects.Count - 1 - i).ToString());
                AppendAttribute(n, "Function", so._Function.ToString());

                SerializeXml(so._Shape, true, n, node.OwnerDocument);
            }
        }
        private static void DeserializeShapeComposition(ref ShapeComposition shapeComposition, XmlNode node)
        {
           // shapeComposition._Objects = new List<ShapeComposition.ShapeObject>();

            int idx = 0;
            foreach(XmlNode n in node.ChildNodes)
            {
                if(n.Name == "Shape")
                {
                    if (GetAttributeValue<int>(n, "Nr", int.TryParse) != idx)
                        throw new Exception("ShapeComposition: Wrong node order");

                    
                    ShapeComposeFunction function = (ShapeComposeFunction)Enum.Parse(typeof(ShapeComposeFunction), (n as XmlElement).GetAttribute("Function"));

                    Shape shape = DeserializeXml(n.FirstChild, true) as Shape;

                    if (idx == 0)
                    {
                        //Transformation2D transform = shapeComposition.Transform;
                        shapeComposition = new ShapeComposition(shape);
                        //shapeComposition._Transform = transform;
                    }
                    else
                        shapeComposition.Add(shape, function);

                    idx++;
                }
            }

            foreach (XmlNode n in node.ChildNodes)
            {
                if (n.Name == "Transform")
                {
                    DeserializeTransform(n, shapeComposition._Transform, false);
                    break;
                }
            }
            
        }

        private static void SerializeSpriteShape(SpriteShape spriteShape, XmlNode node)
        {
            AppendAttribute(node, "Texture", spriteShape.TextureAssetName);
            AppendAttribute(node, "SourceRect", (spriteShape.SourceRect == null) ? "null" : spriteShape.SourceRect.Value.UnParse());
            XmlNode n = node.AppendChild(node.OwnerDocument.CreateElement(spriteShape._Condition.GetTypeName()));
            if (spriteShape._Condition is AlphaThreshold)
            {
                AlphaThreshold c = (AlphaThreshold)spriteShape._Condition;
                AppendAttribute(n, "Value", c.AlphaThresholdValue.ToString());
                AppendAttribute(n, "Invert", c.IsInverted.ToString());
            }
            else if (spriteShape._Condition is ColorMap)
            {
                ColorMap c = (ColorMap)spriteShape._Condition;
                AppendAttribute(n, "Value", c.Color.UnParse());
                AppendAttribute(n, "Invert", c.IsInverted.ToString());
            }
            else
            {
                throw new NotImplementedException("the SpriteShape Condition can not be serialized");
            }
        }
        private static void DeserializeSpriteShape(SpriteShape spriteShape, XmlNode node)
        {
            XmlElement e = (node as XmlElement);
            spriteShape._Border = new List<Vector2>();
            spriteShape.TextureAssetName = e.GetAttribute("Texture");
            string sourceRect = e.GetAttribute("SourceRect");

            if (sourceRect == "null")
                spriteShape.SourceRect = null;
            else
                spriteShape.SourceRect = ParseHelper.ParseRectangle(sourceRect);

            foreach (XmlNode n in node.ChildNodes)
            {
                switch (n.Name)
                {
                    case AlphaThreshold.TypeName:
                        spriteShape._Condition = new AlphaThreshold(
                            GetAttributeValue<byte>(n, "Value", byte.TryParse),
                            GetAttributeValue<bool>(n, "Invert", bool.TryParse));
                        return;
                    case ColorMap.TypeName:
                        spriteShape._Condition = new ColorMap(
                            GetAttributeValue<Color>(n, "Value", ParseHelper.TryParse),
                            GetAttributeValue<bool>(n, "Invert", bool.TryParse));
                        return;
                }
            }
        }

        private static void SerializeTriangle(Triangle triangle, XmlNode node)
        {
            AppendAttribute(node, "Point1", triangle._Point1.UnParse());
            AppendAttribute(node, "Point2", triangle._Point2.UnParse());
            AppendAttribute(node, "Point3", triangle._Point3.UnParse());
        }
        private static void DeserializeTriangle(Triangle triangle, XmlNode node)
        {
            triangle.ChangePoints(
                GetAttributeValue<Vector2>(node, "Point1", ParseHelper.TryParse) + triangle.Position,
                GetAttributeValue<Vector2>(node, "Point2", ParseHelper.TryParse) + triangle.Position,
                GetAttributeValue<Vector2>(node, "Point3", ParseHelper.TryParse) + triangle.Position);
        }

        private static void SerializeRect(Rect rect, XmlNode node)
        {
            AppendAttribute(node, "Width", rect.Width.ToString());
            AppendAttribute(node, "Height", rect.Height.ToString());
        }
        private static void DeserializeRect(Rect rect, XmlNode node)
        {
            rect.ChangeDimension(
                GetAttributeValue<float>(node, "Width", float.TryParse),
                GetAttributeValue<float>(node, "Height", float.TryParse));
        }

        private static void SerializeEllipse(Ellipse ellipse, XmlNode node)
        {
            AppendAttribute(node, "RadiusH", ellipse.HorizontalRadius.ToString());
            AppendAttribute(node, "RadiusV", ellipse.VerticalRadius.ToString());
        }
        private static void DeserializeEllipse(Ellipse ellipse, XmlNode node)
        {
            ellipse.ChangeRadius(
                GetAttributeValue<float>(node, "RadiusH", float.TryParse),
                GetAttributeValue<float>(node, "RadiusV", float.TryParse));
        }

        private static void SerializeCircle(Ellipse ellipse, XmlNode node)
        {
            AppendAttribute(node, "Radius", ellipse._MajorRadius.ToString());
        }

        private static void DeserializeCircle(Ellipse ellipse, XmlNode node)
        {
            if (node.Attributes["Radius"] != null)
            {
                float radius = GetAttributeValue<float>(node, "Radius", float.TryParse);
                ellipse.ChangeRadius(radius, radius);
            }
            else
            {
                DeserializeEllipse(ellipse, node);
            }
        }

        private static void SerializeSpline(Spline spline, XmlNode node)
        {
            foreach (SplinePoint p in spline._Points)
            {
                XmlNode n = node.AppendChild(node.OwnerDocument.CreateElement("SplinePoint"));
                AppendAttribute(n, "Position", p.Position.UnParse());
                AppendAttribute(n, "Smooth", p.IsHandler.ToString());
            }
        }
        private static void DeserializeSpline(Spline spline, XmlNode node)
        {
            spline._Lines = new List<Line>();
            spline._Points = new List<SplinePoint>();
            foreach (XmlNode n in node)
            {
                if (n.Name == "SplinePoint")
                {
                    SplinePoint p = new SplinePoint(
                        GetAttributeValue<Vector2>(n, "Position", ParseHelper.TryParse),
                        GetAttributeValue<bool>(n, "Smooth", bool.TryParse));
                    spline._Points.Add(p);
                }
            }
            spline.SetupLines();
            spline.FitBounding();
        }


        private static void SerializeLineStrip(LineStrip lineStrip, XmlNode node)
        {
            foreach (Vector2 p in lineStrip._Points)
            {
                XmlNode n = node.AppendChild(node.OwnerDocument.CreateElement("Vertex"));
                AppendAttribute(n, "Position", p.UnParse());
            }
        }
        private static void DeserializeLineStrip(LineStrip lineStrip, XmlNode node)
        {
            lineStrip._Lines = new List<Line>();
            lineStrip._Points = new List<Vector2>();
            foreach (XmlNode n in node.ChildNodes)
            {
                if (n.Name == "Vertex")
                {
                    Vector2 vertex = GetAttributeValue<Vector2>(n, "Position", ParseHelper.TryParse);
                    lineStrip._Points.Add(vertex);
                }
            }
            lineStrip.SetupLines();
            lineStrip.FitBounding();
        }

        private static void SerializeLine(Line line, XmlNode node)
        {
            AppendAttribute(node, "Start", line.StartPoint.UnParse());
            AppendAttribute(node, "End", line.EndPoint.UnParse());
        }
        private static void DeserializeLine(Line line, XmlNode node)
        {
            line.StartPoint = GetAttributeValue<Vector2>(node, "Start", ParseHelper.TryParse);
            line.EndPoint   = GetAttributeValue<Vector2>(node, "End", ParseHelper.TryParse);
        }


        private static void SerializeDot(Dot dot, XmlNode node)
        {
            AppendAttribute(node, "Position", dot.Position.UnParse());
        }
        private static void DeserializeDot(Dot dot, XmlNode node)
        {
            dot.Position = GetAttributeValue<Vector2>(node, "Position", ParseHelper.TryParse);
        }

        #endregion

        /// <summary>
        /// Serializes a Transform object to Xml
        /// </summary>
        /// <param name="transform">the transform to serialize</param>
        /// <param name="saveDimensions">if true, the width and height of the transform is saved, otherwise not</param>
        /// <param name="parent">the parent node where the transform node is appended to</param>
        /// <param name="document">the document which contains the parent</param>
        /// <returns>the xml node of the transformation which is appended to the parent node after the method has been processed</returns>
        public static XmlNode SerializeTransformation(Transformation2D transform, bool saveDimensions, XmlNode parent, XmlDocument document)
        {
            XmlNode node = parent.AppendChild(document.CreateElement("Transform"));
            AppendAttribute(node, document, "Position", transform._Position.UnParse());
            AppendAttribute(node, document, "Origin", transform._Origin.UnParse());
            AppendAttribute(node, document, "Rotation", transform._Rotation.Radians.ToString());
            AppendAttribute(node, document, "Scale", transform._Scale.UnParse());

            if (saveDimensions)
            {
                AppendAttribute(node, document, "Width", transform._Width.ToString());
                AppendAttribute(node, document, "Height", transform._Height.ToString());
            }
            return node;
        }

        /// <summary>
        /// Fills a Transformation2D object with data from xml
        /// </summary>
        /// <param name="node">the node which contains the transform data</param>
        /// <param name="transform">the instance which shall be filled with data</param>
        /// <param name="loadDimensions">if true, the width and height of the transform is tried to be loaded, otherwise not</param>
        public static void DeserializeTransform(XmlNode node, Transformation2D transform, bool loadDimensions)
        {
            if (node.Name != "Transform")
                throw new Exception("Wrong XmlNode identifier");

            transform._Position = GetAttributeValue<Vector2>(node, "Position", ParseHelper.TryParse);
            transform._Origin   = GetAttributeValue<Vector2>(node, "Origin", ParseHelper.TryParse);
            transform._Rotation = new Angle(GetAttributeValue<float>(node, "Rotation", float.TryParse));
            transform._Scale    = GetAttributeValue<Vector2>(node, "Scale", ParseHelper.TryParse);

            if (loadDimensions)
            {
                transform._Width = GetAttributeValue<float>(node, "Width", float.TryParse);
                transform._Height = GetAttributeValue<float>(node, "Height", float.TryParse);
            }
            transform._IsDirty = true;
        }

        private static void AppendAttribute(XmlNode node, string name, string value)
        {
            AppendAttribute(node, node.OwnerDocument, name, value);
        }
        private static void AppendAttribute(XmlNode node, XmlDocument document, string name, string value)
        {
            XmlAttribute attribute = node.OwnerDocument.CreateAttribute(name);
            attribute.Value = value;

            node.Attributes.Append(attribute);
        }

        private static T GetAttributeValue<T>(XmlNode node, string name, TryParseDelegate<T> tryParse)
        {
            XmlAttribute a = node.Attributes.GetNamedItem(name) as XmlAttribute;
            T result;
            if (a == null || !tryParse(a.Value, out result))
                return default(T);

            return result;
        }

    }

#endif

}
